package hyl.ext.base;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import hyl.base.cache2.TimeCache;
import hyl.core.MyFun;
import hyl.core.conf.MyConst;

/**
 * 
 * @ClassName: SessionFactory
 * @Description: session 池
 * @author zoudy
 * @date 2018年2月8日 下午5:44:08
 * @version V1.0
 */
public class SessionFactory {
//不同项目的redis 的端口号要不同
	private static final Logger logger = LoggerFactory.getLogger(SessionFactory.class);
	public static final String LOGIN_TEMP = "0";// 安全模式 一旦关闭浏览器就需要再次登录
	public static final String LOGIN_REMEMBER = "1";// 记忆模式 2小时内免登录
	public static final int TYPE_DEL = 2; // 登录后立即删除
	public static final int TYPE_UPDATE = 1;// 更新过期时间
	public static final int TYPE_NOCHANGE = 0;// 不改变原来的过期时间
	public static final String STATE_INIT = "00000000";// 初始化
	public static final String STATE_INVALID = "1";// 失效的token
	public static final String STATE_OK = "2";// 有效的token
	public static String SessionKey = "HYLSESSIONTOKENID";// 用户cookie
	public static String SessionGROUP = "SYSTOKENOBJECT";// token存储在redis group 和request attribute,方便备份和后续使用
	public static final String SessionSTAMP = "SYSTOKENSTAMP";// 交互流水号字段名
	public static final Long EXPIRED30m = 1800000L;// ip被禁以后的再次登录间隔时间 (ms)
	public static Long EFFECTTIMES = 3600000L;// 登录的有效时长 免登录时长(ms)
	public static final Long EXPIRED30S = 30000L;// 初始化时 过期时长(ms)
	public static final Long EXPIRED6m = 300000L;// 初始化时 过期时长(ms)
	private static String _cache = "cache1";
	public static final int MaxErrTimes = 5;

	// 未登录的集合
	/**
	 * 已经登录的集合
	 * 
	 * 正常页面登录的会话集合
	 */
	private static TimeCache<MySession> _loginsession = null;
	/**
	 * 用户-登录 单点登录时的会话映射 集合 (用于后台登录)
	 * 
	 * 一个用户只能对应一个会话,新的会话会覆盖老的会话
	 * 
	 * userid,Sessionid
	 */
	// protected static CacheString _ssosession = new CacheString("单点库", "ssoses");
	// _ssosession 仅保存 单点登录的用户会话 , 正常登录不保存
	protected static Map<Integer, String> _ssosession = new ConcurrentHashMap<>();
	private static SessionFactory _factory = null;

	// 单例模式工厂
	public static SessionFactory getInstance() {
		return getInstance(_cache);
	}

	@SuppressWarnings("unchecked")
	/**
	 * 
	 * @param cachealias cache配置文件中的别名
	 * @return
	 */
	public static SessionFactory getInstance(String cachealias) {
		if (_factory == null) {
			// 在生态系统中,可以定义不同的sessionid 使得多系统在同一域名下可以相互访问,而不会导致sessionid冲突
			String sesid = MyConst.getInstance().get("SESSIONID");
			if (!MyFun.isEmpty(sesid))
				SessionKey = sesid;
			String sesgp = MyConst.getInstance().get("SESSIONGROUP");
			if (!MyFun.isEmpty(sesgp))
				SessionGROUP = sesgp;
			_loginsession = TimeCache.getInstance(cachealias);

			_factory = new SessionFactory();
		}
		return _factory;
	}

	private SessionFactory() {
		load();
	}

//删除该uuid
	public static void delToken(String sessionid) {
		MySession sessoin = _loginsession.get(sessionid);
		if (sessoin != null)
			delSession(sessoin);
	}


	/**
	 * 
	 * 适用于单点登录
	 *
	 * 查询当前用户的 单点登录的会话
	 * 
	 * 
	 * 
	 * sso.session
	 * 
	 * @param userid
	 * @return
	 */
	public static MySession getSsoSessionByUserid(Integer userid) {
		String sessionid = _ssosession.get(userid);
		//MyFun.print(sessionid);
		if (sessionid == null)
			return null;
		return _loginsession.get(sessionid);
	}
	

	/**
	 * 如果过期自动创新, 适用于单点登录
	 * 
	 * @param userid
	 * @return
	 */
	public static MySession getSsoSession(Integer userid, String ip) {
		MySession ms = getSsoSessionByUserid(userid);
		ms = chk会话(ms, ip);
		//MyFun.printJson(ms);
		// 如果会话不存在 创建会话
		if (ms == null) {
			ms = new MySession(ip);//
			ms.setUserId(userid);
			// 角色,姓名 手机,状态等其他信息在 ssa 中 载入
			// Response.addCookie(res, SessionKey, ms.token, EFFECTTIMES.intValue());
			//MyFun.printJson(ms);
			_ssosession.put(userid, ms.token);
			_loginsession.put(ms.token, ms);
		} else {
			ms.ip = ip;
		}
	
		// 这里不要调用 session.start 这个就是获取 没有启动的意义
		return ms;
	}
	

	public static void delSession( MySession session) {
		if(session==null|| MyFun.isEmpty(session.userId)) return;
		_ssosession.remove(session.userId);
		_loginsession.remove(session.token);
		session=null;
		
	}
	/**
	 * 放入 attribute
	 * 
	 * @param req
	 * @param session
	 */
	public static void putSession(HttpServletRequest req, MySession session) {
		req.setAttribute(SessionGROUP, session);
		req.getSession().setAttribute(SessionGROUP, session);
	}

	/**
	 * 补充cookie取
	 * 
	 * @param req
	 * @return
	 */
	public static MySession getSession(HttpServletRequest req) {
		MySession session = (MySession) req.getAttribute(SessionGROUP);
		if (session == null&&req.getSession() != null)
				session = (MySession) req.getSession().getAttribute(SessionGROUP);
		// 如果超时过期,必须要删除原来的session
		if (session!=null&&session.is过期()) {
			delSession(req, session);
			return null;
		}
		return session;
	}

	public static void delSession(HttpServletRequest req, MySession session) {
		delSession(session);
		req.removeAttribute(SessionGROUP);
		req.getSession().removeAttribute(SessionGROUP);
	}

	// 判断cookid中的tokenid 对应的token
	/**
	 * 登录之前 靠ip:端口 主要靠uuid 为key 保存 session
	 */
	public static MySession getSession(HttpServletRequest req, HttpServletResponse res) {

		MySession session = getSession(req);
		if (session != null)
			return session;
	
		String sessionid = Response.getCookie(req, SessionKey);
		if (MyFun.isEmpty(sessionid)) {
			sessionid = req.getHeader(SessionKey);
		}
		if (MyFun.isEmpty(sessionid)) {
			sessionid = req.getParameter(SessionKey);
		}
		String ip = Response.getIPAddress(req);
		// System.out.println("ip::"+ip+",url:"+req.getServletPath());
		// logger.info("uuid:" + uuid);
		if (!MyFun.isEmpty(sessionid)) {// 如果有令牌 ,1 判断令牌是否有效,2 判断地址是否变更
			session = (MySession) _loginsession.get(sessionid);
			session = chk会话(session, ip);// 过期或不存在都是失效,失效返回null
		}
		if (session == null) {// 如果没有令牌,重新申请一个
			// String ip = Response.getIPAddress(req);
			session = new MySession(ip);//
		}

		Response.addCookie(res, SessionKey, session.token, EFFECTTIMES.intValue());
		_loginsession.put(session.token, session);
		putSession(req, session);
		return session;
	}

	/**
	 * 如果有token就可以获取session 那么对于系统而言是非常危险的
	 * 
	 * 所以流程应该是 先分配一个临时的token给客户端 ， 客户端根据这个token
	 * 
	 * 
	 * 登录换一个token 本方案不考虑ip会被监听到的情况
	 * 
	 */
//	 根据uuid 获取token会话
	public static MySession getSessionById(String sessionid) {
		if (MyFun.isEmpty(sessionid)) return null;
		MySession session = (MySession) _loginsession.get(sessionid);
		return chk会话(session, null);
	}
//	public static MySession getSessionByUserId(String usid) {
//		MySession session = (MySession) _loginset.get(sessionid);
//		return chk会话带移除(session, null);
//	}
	// 强制创建会话是没有意义的
//	public static MySession createSession(HttpServletRequest req) {
//		String ip = Response.getIPAddress(req);
//		MySession session = new MySession(ip);
//		_loginset.put(session.token, session);
//		return session;
//	}

	/**
	 * 如果session 无效 自动移除 <br>
	 * (ip地址不一致 或者 会话过期 算作无效)
	 * 
	 * @param session
	 * @param ip      如果 ip不为空, 那么如果ip与session的ip 不一致 ,
	 * 
	 * @return
	 */
	public static synchronized MySession chk会话(MySession session, String ip) {
		try {
			// USession session = (USession) _loginset.get(uuid);
			// 如果session 不存在退出
			if (session == null)
				return null;
			// 如果超时过期,必须要删除原来的session
			if (session.is过期()) {
				delSession(session);				
				session = null;
				return null;
			}
			// 如果ip 不等 说明重新登录了,或者被冒用了 就创建一个新的session ,但是不删除原来的 session 可能别人还在使用
			if (session.eqIP(ip)) {
				session = null;
				return null;
			}		
			return session;
		} catch (Exception e) {
			return null;
		}
	}

	// 判断是否包含这个 tokenid 但是并不能判断 session 是否有效
	public static boolean isLogin(String sessionid) {
		return _loginsession.containsKey(sessionid);
	}

	// 载入redis 中的cookie缓存
	public static void load() {
		try {
			_loginsession.open(SessionGROUP);
		} catch (Exception e) {
			logger.error("载入cookied 异常 ", e);
		}
	}

}
// 授权
// 令牌
// 资源
// 用户
// 客户端
// 授权服务端
// 资源服务端
// iss：Issuer，发行者
// sub：Subject，主题
// aud：Audience，观众
// exp：Expiration time，过期时间
// nbf：Not before
// iat：Issued at，发行时间
// jti：JWT ID
/*
 * access_token：表示访问令牌，必选项。 token_type：表示令牌类型，该值大小写不敏感，必选项，可以是bearer类型或mac类型。
 * expires_in：表示过期时间，单位为秒。如果省略该参数，必须其他方式设置过期时间。
 * refresh_token：表示更新令牌，用来获取下一次的访问令牌，可选项。 scope：表示权限范围，如果与客户端申请的范围一致，此项可省略。
 */
// String ACCESS_TOKEN ;
// String token_type ;
// String expires_in;
// String scope;
// String refresh_token;